package com.gitee.wsl.compose.chart.geometry.model.cliper

import com.gitee.wsl.compose.chart.geometry.model.GeometryBuffer
import com.gitee.wsl.ext.base.Arrays.arraycopy


/*
 * Copyright 2013 Hannes Janetzek
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * Clip polygons and lines to a rectangle. Output cannot expected to be valid
 * Simple-Feature geometry, i.e. all polygon rings are clipped independently
 * so that inner and outer rings might touch, etc.
 *
 * based on http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html
 */
class TileClipper(
    private var xmin: Float,
    private var ymin: Float,
    private var xmax: Float,
    private var ymax: Float
) {
    fun setRect(xmin: Float, ymin: Float, xmax: Float, ymax: Float) {
        this.xmin = xmin
        this.ymin = ymin
        this.xmax = xmax
        this.ymax = ymax
        mLineClipper.setRect(xmin, ymin, xmax, ymax)
    }

    private val mLineClipper: LineClipper
    private val mGeomOut = GeometryBuffer(10, 1)

    init {
        mLineClipper = LineClipper(xmin, ymin, xmax, ymax)
    }

    fun clip(geom: GeometryBuffer): Boolean {
        if (geom.isPoly) {
            val out = mGeomOut
            out.clear()
            clipEdge(geom, out, LineClipper.LEFT)
            geom.clear()
            clipEdge(out, geom, LineClipper.TOP)
            out.clear()
            clipEdge(geom, out, LineClipper.RIGHT)
            geom.clear()
            clipEdge(out, geom, LineClipper.BOTTOM)
            if (geom.indexPos == 0 && geom.index[0] < 6) return false
        } else if (geom.isLine) {
            val out = mGeomOut
            out.clear()
            val numLines = mLineClipper.clipLine(geom, out)
            val idx = geom.ensureIndexSize(numLines + 1, false)
            arraycopy(out.index, 0, idx, 0, numLines)
            geom.index[numLines] = -1
            val pts = geom.ensurePointSize(out.pointPos shr 1, false)
            arraycopy(out.points, 0, pts, 0, out.pointPos)
            geom.indexPos = out.indexPos
            geom.pointPos = out.pointPos
            if (geom.indexPos == 0 && geom.index[0] < 4) return false
        }
        return true
    }

    private fun clipEdge(`in`: GeometryBuffer, out: GeometryBuffer, edge: Int): Boolean {
        out.startPolygon()
        var outer = true
        var pointPos = 0
        var indexPos = 0
        val n = `in`.index.size
        while (indexPos < n) {
            val len = `in`.index[indexPos]
            if (len < 0) break
            if (len == 0) {
                out.startPolygon()
                outer = true
                indexPos++
                continue
            }
            if (len < 6) {
                pointPos += len
                indexPos++
                continue
            }
            if (!outer) out.startHole()
            when (edge) {
                LineClipper.LEFT -> clipRingLeft(indexPos, pointPos, `in`, out)
                LineClipper.RIGHT -> clipRingRight(indexPos, pointPos, `in`, out)
                LineClipper.TOP -> clipRingTop(indexPos, pointPos, `in`, out)
                LineClipper.BOTTOM -> clipRingBottom(indexPos, pointPos, `in`, out)
            }

            //if (out.index[i] < 6) {
            //	out.index[i] = 0;
            //	//if (out.indexPos > 0)
            //	//	out.indexPos--;
            //	// TODO if outer skip holes
            //}
            pointPos += len
            outer = false
            indexPos++
        }
        return true
    }

    private fun clipRingLeft(
        indexPos: Int,
        pointPos: Int,
        `in`: GeometryBuffer,
        out: GeometryBuffer
    ) {
        val end = `in`.index[indexPos] + pointPos
        var px = `in`.points[end - 2]
        var py = `in`.points[end - 1]
        var i = pointPos
        while (i < end) {
            val cx = `in`.points[i++]
            val cy = `in`.points[i++]
            if (cx > xmin) {
                /* current is inside */
                if (px > xmin) {
                    /* previous was inside */
                    out.addPoint(cx, cy)
                } else {
                    /* previous was outside, add edge point */
                    out.addPoint(xmin, py + (cy - py) * (xmin - px) / (cx - px))
                    out.addPoint(cx, cy)
                }
            } else {
                if (px > xmin) {
                    /* previous was inside, add edge point */
                    out.addPoint(xmin, py + (cy - py) * (xmin - px) / (cx - px))
                }
                /* else skip point */
            }
            px = cx
            py = cy
        }
    }

    private fun clipRingRight(
        indexPos: Int,
        pointPos: Int,
        `in`: GeometryBuffer,
        out: GeometryBuffer
    ) {
        val len = `in`.index[indexPos] + pointPos
        var px = `in`.points[len - 2]
        var py = `in`.points[len - 1]
        var i = pointPos
        while (i < len) {
            val cx = `in`.points[i++]
            val cy = `in`.points[i++]
            if (cx < xmax) {
                if (px < xmax) {
                    out.addPoint(cx, cy)
                } else {
                    out.addPoint(xmax, py + (cy - py) * (xmax - px) / (cx - px))
                    out.addPoint(cx, cy)
                }
            } else {
                if (px < xmax) {
                    out.addPoint(xmax, py + (cy - py) * (xmax - px) / (cx - px))
                }
            }
            px = cx
            py = cy
        }
    }

    private fun clipRingTop(
        indexPos: Int,
        pointPos: Int,
        `in`: GeometryBuffer,
        out: GeometryBuffer
    ) {
        val len = `in`.index[indexPos] + pointPos
        var px = `in`.points[len - 2]
        var py = `in`.points[len - 1]
        var i = pointPos
        while (i < len) {
            val cx = `in`.points[i++]
            val cy = `in`.points[i++]
            if (cy < ymax) {
                if (py < ymax) {
                    out.addPoint(cx, cy)
                } else {
                    out.addPoint(px + (cx - px) * (ymax - py) / (cy - py), ymax)
                    out.addPoint(cx, cy)
                }
            } else {
                if (py < ymax) {
                    out.addPoint(px + (cx - px) * (ymax - py) / (cy - py), ymax)
                }
            }
            px = cx
            py = cy
        }
    }

    private fun clipRingBottom(
        indexPos: Int,
        pointPos: Int,
        `in`: GeometryBuffer,
        out: GeometryBuffer
    ) {
        val len = `in`.index[indexPos] + pointPos
        var px = `in`.points[len - 2]
        var py = `in`.points[len - 1]
        var i = pointPos
        while (i < len) {
            val cx = `in`.points[i++]
            val cy = `in`.points[i++]
            if (cy > ymin) {
                if (py > ymin) {
                    out.addPoint(cx, cy)
                } else {
                    out.addPoint(px + (cx - px) * (ymin - py) / (cy - py), ymin)
                    out.addPoint(cx, cy)
                }
            } else {
                if (py > ymin) {
                    out.addPoint(px + (cx - px) * (ymin - py) / (cy - py), ymin)
                }
            }
            px = cx
            py = cy
        }
    }
}

